package com.dataeco.api.facility;


public class Snowflake {
    private static final long twepoch = 1288834974657L;

    private static final int SEQUENCE_BITS = 12;
    private static final int NODE_BITS = 5;
    private static final int REGIN_BITS = 5;

    private static final int NODE_SHIFT = SEQUENCE_BITS;
    private static final int REGIN_SHIFT = SEQUENCE_BITS + NODE_BITS;
    private static final int TIME_SHIFT = SEQUENCE_BITS + NODE_BITS + REGIN_BITS;

    private static final long MAX_NODE = ~(-1L << NODE_BITS);
    private static final long MAX_REGIN = ~(-1L << REGIN_BITS);

    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);

    private long regin = 1;
    private long node = 1;
    private long sequence = 0L;
    private long lastTime = -1L;
    public Snowflake(long regin, long node) {
        if (regin > MAX_REGIN || regin < 0) {
            throw new IllegalArgumentException(String.format("regin id cannot greater than %d or less than 0", MAX_REGIN));
        }
        if (node > MAX_NODE || node < 0) {
            throw new IllegalArgumentException(String.format("node id cannot be greater than %d or less than 0", MAX_NODE));
        }
        this.regin = regin;
        this.node = node;
    }

    public synchronized long next() {
        long time = System.currentTimeMillis();
        if (time < lastTime) {
            throw new RuntimeException("Clock moved backwards.");
        }
        if (lastTime == time) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            if (sequence == 0) {
                time = tilNextMillis(lastTime);
            }
        } else {
            sequence = 0L;
        }

        lastTime = time;

        return ((time - twepoch) << TIME_SHIFT) | (regin << REGIN_SHIFT) | (node << NODE_SHIFT) | sequence;
    }

    public long nextLong() {
        return next();
    }

    public String nextHexString() {
        return Long.toHexString(next());
    }

    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}
